package com.chenju.javastatic.adapter;

import com.chenju.javastatic.common.enums.EasyCode;
import com.chenju.javastatic.utils.EasyListUtils;
import com.chenju.javastatic.utils.EasyVisitUtils;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;

import java.util.ArrayList;
import java.util.List;

public class MethodVisitorAdapter extends MethodVisitor {
    public int access;
    public String name;
    public String desc;
    public String signature;
    public String[] exceptions;
    public List<ParameterNode> parameters;
    public List<AnnotationVisitorAdapter> visibleAnnotations;
    public List<AnnotationVisitorAdapter> invisibleAnnotations;
    public String owner;
    public List<MethodInsnVisitorAdapter> methodInsns;
    public OuterMethodVisitorAdapter outerMethod;

    public List<String> requestPaths;
    public List<String> requestMethods;

    public boolean isMapping = false;

    public MethodVisitorAdapter() {
        this(Opcodes.ASM9);
        if (this.getClass() != MethodVisitorAdapter.class) {
            throw new IllegalStateException();
        }
    }

    public MethodVisitorAdapter(int api) {
        super(api);
    }

    public MethodVisitorAdapter(int access, String name, String descriptor, String signature, String[] exceptions, String owner, OuterMethodVisitorAdapter outerMethod) {
        this(Opcodes.ASM9, access, name, descriptor, signature, exceptions, owner, outerMethod);
        if (this.getClass() != MethodVisitorAdapter.class) {
            throw new IllegalStateException();
        }
    }

    public MethodVisitorAdapter(int api, int access, String name, String descriptor, String signature, String[] exceptions, String owner, OuterMethodVisitorAdapter outerMethod) {
        super(api);
        this.access = access;
        this.name = name;
        this.owner = owner;
        this.desc = descriptor;
        this.signature = signature;
        this.exceptions = exceptions;
        this.methodInsns = new ArrayList<>();
        this.visibleAnnotations = new ArrayList<>();
        this.outerMethod = outerMethod;
    }

    public void visitParameter(String name, int access) {
        if (this.parameters == null) {
            this.parameters = new ArrayList<>(5);
        }

        this.parameters.add(new ParameterNode(name, access));
    }

    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        AnnotationVisitorAdapter annotation = new AnnotationVisitorAdapter(descriptor);
        if (visible) {
            this.visibleAnnotations = EasyListUtils.add(this.visibleAnnotations, annotation);
        } else {
            this.invisibleAnnotations = EasyListUtils.add(this.invisibleAnnotations, annotation);
        }

        return annotation;
    }

    @Override
    public void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {
        if (!EasyVisitUtils.isVisitMethod(name)){
            return;
        }
        int opcode = opcodeAndSource & -257;
        String invokeType = null;
        switch (opcode) {
            case Opcodes.INVOKEVIRTUAL:
                invokeType = EasyCode.INVOKEVITUAL_TYPE;
                break;
            case Opcodes.INVOKESPECIAL:
                invokeType = EasyCode.INVOKESPECIAL_TYPE;
                break;
            case Opcodes.INVOKESTATIC:
                invokeType = EasyCode.INVOKESTATIC_TYPE;
                break;
            case Opcodes.INVOKEINTERFACE:
                invokeType = EasyCode.INVOKEINTERFACE_TYPE;
                break;
        }
        MethodInsnVisitorAdapter methodInsn = new MethodInsnVisitorAdapter(opcode, owner, name, descriptor, isInterface, invokeType);
        if (EasyVisitUtils.isVisitMethod(name) && EasyVisitUtils.isVisitClass(owner)) {
            if (!this.methodInsns.contains(methodInsn)){
                this.methodInsns.add(methodInsn);
            }
        }
    }

    @Override
    public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
        if (isLambdaMetaFactory(bootstrapMethodHandle.getOwner()) && isLambdaMetaFactoryMethod(bootstrapMethodHandle.getName())){
            // 判断是否是Java编译器自动生成的lambda方法，如过是则查找lambda与原方法的关系
            for (Object obj : bootstrapMethodArguments) {
                if (obj instanceof Handle) {
                    String handleOwner = ((Handle) obj).getOwner();
                    String handleName = ((Handle) obj).getName();
                    String handleDesc = ((Handle) obj).getDesc();
                    if (!EasyVisitUtils.isVisitClass(handleOwner) || !EasyVisitUtils.isVisitMethod(handleName)){
                        continue;
                    }
                    MethodInsnVisitorAdapter methodInsn = new MethodInsnVisitorAdapter(handleOwner, handleName, handleDesc, false, EasyCode.INVOKEDYNAMIC_TYPE);
                    if (!this.methodInsns.contains(methodInsn)){
                        this.methodInsns.add(methodInsn);
                    }
                }
            }
        }
    }

    @Override
    public void visitEnd() {
        if (this.visibleAnnotations != null){
            for (AnnotationVisitorAdapter annotation : this.visibleAnnotations) {
                this.isMapping = annotation.isRequestMapping || annotation.isMapping || this.isMapping;
                if (annotation.isRequestMapping){
                    this.requestMethods = (List<String>) annotation.valuesMap.get("method");
                    this.requestPaths = (List<String>) (annotation.valuesMap.get("path") == null ? annotation.valuesMap.get("value") : annotation.valuesMap.get("path"));
                } else if (annotation.isMapping) {
                    String annotationSimpleName = annotation.simpleName == null ? "" :annotation.simpleName;
                    this.requestMethods = EasyListUtils.asArrayList(annotationSimpleName.replace("Mapping", "").toUpperCase());
                    this.requestPaths = (List<String>) (annotation.valuesMap.get("path") == null ? annotation.valuesMap.get("value") : annotation.valuesMap.get("path"));
                }
            }
        }
    }

    public boolean isLambdaMetaFactory(String owner) {
        return "java/lang/invoke/LambdaMetafactory".equals(owner);
    }

    public boolean isLambdaMetaFactoryMethod(String name) {
        return "metafactory".equals(name);
    }
}
